背景

bohrium 前端项目在 Next.js 中使用 tolgee 管理国际化文案时,发现一个问题:

SSR 页面返回的 HTML 源代码中出现了所有语言的所有文案

![[Pasted image 20260305161212.png]]

导致:

  1. 严重影响 SEO:英文状态,源码里面含大量中文,难以让搜索引擎判定页面语言
  2. 严重影响网页性能:html 体积太大,传输和渲染都有很大影响

代码分析

bohrium-domains/src/locales/tolgee.ts

![[Pasted image 20260305161228.png]]

bohrium-next-app/src/locales/tolgee/shared.ts

![[Pasted image 20260305161239.png]]

即便使用了 import() 动态引入,tolgee SSR 仍然加载了所有的资源

解决方案

第一步 只加载当前语言的文案

拿取 Cookie 中的语言设置,只给 tolgee 传入当前语言的资源

结果:体积少一半(中+英 ➡️ 中/英);中英不掺杂

副作用:无。由于我们现在网站是切换语言刷新浏览器,所以不会导致文案缺失

![[Pasted image 20260305161302.png]]

第二步 只加载指定命名空间的文案

第一步少了一半文案,但仍然包含所有的中或英文案,可以进一步优化为加载使用到的几个命名空间下的文案即可

结果:体积少2/3(30+命名空间文案 ➡️ 3~8个命名空间文案)

副作用:如果没有在服务端加载首页用到的命名空间,会导致刷新页面出现1秒左右无文案状态(显示key)

默认只加载 common 和侧边栏的资源:

![[Pasted image 20260305161319.png]]

不同的页面动态加载不同命名空间的文案

![[Pasted image 20260305161330.png]]

例如文献详情页加载了 paper-detail 命名空间

![[Pasted image 20260305161344.png]]

以上是服务端加载,会直接包含在html源代码中

服务端没有加载的命名空间,如果客户端使用到了,会在客户端动态加载:

![[Pasted image 20260305161355.png]]

上图代码中:

staticData = {
    'zh:common': {
        'cancel': '取消',
        ...
    },
    ...
}
allStaticData = {
    'zh:chat': () => import('zh/chat.json'),
    'zh:wiki': () => import('zh/wiki.json'),
    ...
}

使用指南

bohrium-next-app/src/app 下:

某个路径下公用的命名空间(layout.tsx 中)

export default async function SharedTabLayout({ children }: { children: React.ReactNode }) {
    await loadNamespaces(['journal']);

    ...
}

某个页面的命名空间(page.tsx 中)

export default async function Page({ params }: PageProps) {
    await initServer();
    await loadNamespaces(['paper-detail']);
    
    ...
}

现存问题

  1. 英文文案中出现中文key,导致html源代码中有中文

![[Pasted image 20260305161422.png]]

  1. 英文文案中出现中文value,导致html源代码中有中文

![[Pasted image 20260305161433.png]]

  1. 命名空间划分混乱:经常发现一个页面使用多个命名空间

为什么以命名空间为最小单元

是否可以只加载页面中用到的文案?

Q:html只返回当前语言的文案,但是是返回的所有文案(即使当前页面没有使用),能不能只返回当前页面使用到的文案?我的思路(仅供参考): @server.tsx (18-23) 这里的observerOptions是不是有相关配置?

Claude Sonnet 4.5:

observerOptions 确实不是用来控制加载的,而是控制 DOM 观察行为的。要实现按页面加载特定命名空间,需要利用 Tolgee 的 namespace (ns) 机制。

我看到了项目的使用模式:

现在的问题是 layout.tsx 中的 tolgee.loadRequired() 会加载所有命名空间。我们需要实现按需加载。

Q:嗯,这样可以实现,但是要修改很多之前的代码。而且最小粒度是namespace,一个页面并不会用到namespace所有文案。所以我在想有没有办法能自动扫描当前html中用了哪些文案,并只携带用到的文案

Claude Sonnet 4.5:

非常好的想法!你提出了一个更深层次的优化需求。让我分析一下自动扫描并只携带使用到的翻译 key的可行性:

方案 1:构建时静态分析(最优,但复杂)

原理:在构建时扫描所有组件,提取使用的翻译 key

实现步骤:

  1. 编写 Babel/SWC 插件扫描 t() 和 getTranslate() 调用

  2. 生成每个页面的 key 使用清单

  3. 在 SSR 时只传递使用的 key

优点:

缺点:

方案 2:两次渲染 + 运行时追踪

原理:先渲染一次收集使用的 key,然后只传递这些 key

优点:

缺点:

方案 3:混合方案(推荐平衡)

原理:

  1. 开发时:手动标记关键页面的 key 列表

  2. 构建时:自动扫描收集(可选)

  3. 运行时:动态加载缺失的 key

我的建议

基于收益/成本分析:

短期方案(推荐)✅

继续使用 Namespace 方案,因为:

  1. 已经减少了 70% 的数据量

  2. 实现简单,维护成本低

  3. 对大多数应用来说已经足够优化

中期方案(可选)

针对超大页面做定制优化:

长期方案(如果确实需要)

考虑实现静态分析(方案 1),但需要:

  1. 评估团队技术栈(是否有编写编译器插件的能力)

  2. 考虑维护成本

  3. 先做 POC 验证可行性

我的最终建议

  1. 先用 Namespace 方案(已实现),它能解决 80% 的问题

  2. 监控实际效果:测量首屏加载时间、HTML 大小

  3. 如果仍有性能瓶颈:

  1. 只有在有明确收益时,才考虑实现完整的 key 级自动追踪

你觉得呢?需要我帮你实现简易的 key 级过滤方案吗?还是你想先验证一下当前 namespace 方案的实际效果?

Q:请使用context7和联网搜索,确定下Tolgee真的没有这个功能吗?我认为这是个很通用的功能,业界应该有相关的实现了

Claude Sonnet 4.5:

你说得对!这确实是一个很常见的需求。让我深入调查一下 Tolgee 和业界的解决方案。

好的,我做了全面的调查。以下是结论:

📊 调查结果

✅ 确认:Tolgee 没有自动提取使用 key 的功能

经过多次搜索和文档查阅,Tolgee 确实不支持自动扫描 HTML/组件并只加载实际使用的翻译 key。它的最小粒度是 namespace。

🔍 业界现状

虽然这是个合理的需求,但业界也没有主流 i18n 库在运行时实现这个功能。原因是:

1. 技术难点

2. 现有工具都是"构建时"方案

业界有一些构建时的工具来优化翻译:

这些工具的目的是:

但它们不能:

💡 为什么没有运行时方案?

我理解你的想法非常合理,但运行时自动提取使用 key 有根本性困难:

// 场景 1: 动态 key

const key = Math.random() > 0.5 ? 'key1' : 'key2';

t(key); // 怎么在渲染前知道会用哪个?

// 场景 2: 条件渲染

{user.isPremium && {t('premium_feature')}}

// 不同用户看到不同内容

// 场景 3: 循环生成

items.map(item =>

{t(item_${item.id})}
)

// key 是动态拼接的

结论

  1. Tolgee 确实没有这个功能(已确认)

  2. 业界也没有运行时的自动方案(技术限制)

  3. Namespace 方案已经是业界标准(所有主流库都这样)

  4. 可以通过拆分 namespace 进一步优化(页面级粒度)

我的建议:先实施 namespace 方案,看实际效果。如果某些 namespace 确实很大(>100KB),再考虑拆分或定制优化。你觉得呢?要不要我帮你分析一下当前哪些 namespace 最大,优先拆分那些?